<template>
  <div ref="wrap">
    <div :style="leftSwitch" v-if="isHorizontal" :class="leftSwitchClass" @click="leftSwitchClick">
      <slot name="left-switch"></slot>
    </div>
    <div :style="rightSwitch" v-if="isHorizontal" :class="rightSwitchClass" @click="rightSwitchClick">
      <slot name="right-switch"></slot>
    </div>
    <div ref="realBox" :style="pos" @mouseenter="enter" @mouseleave="leave" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
      <div ref="slotList" :style="float">
        <slot></slot>
      </div>
      <div v-html="copyHtml" :style="float"></div>
    </div>
  </div>
</template>
<script>
require('comutils/animationFrame')()
const arrayEqual = require('comutils/arrayEqual')
const copyObj = require('comutils/copyObj')
export default {
  data () {
    return {
      xPos: 0,
      yPos: 0,
      delay: 0,
      copyHtml: '',
      height: 0,
      width: 0,
      realBoxWidth: 0,
      reqFrame: null,
      singleWaitTime: null,
      isHover: false
    }
  },
  props: {
    data: {
      type: Array,
      default: () => {
        return []
      }
    },
    classOption: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  computed: {
    leftSwitchState () {
      return this.xPos < 0
    },
    rightSwitchState () {
      return Math.abs(this.xPos) < (this.realBoxWidth - this.width)
    },
    leftSwitchClass () {
      return this.leftSwitchState ? '' : this.options.switchDisabledClass
    },
    rightSwitchClass () {
      return this.rightSwitchState ? '' : this.options.switchDisabledClass
    },
    leftSwitch () {
      return {
        position: 'absolute',
        margin: `${this.height / 2}px 0 0 -${this.options.switchOffset}px`,
        transform: 'translate(-100%,-50%)'
      }
    },
    rightSwitch () {
      return {
        position: 'absolute',
        margin: `${this.height / 2}px 0 0 ${this.width + this.options.switchOffset}px`,
        transform: 'translateY(-50%)'
      }
    },
    float () {
      return this.isHorizontal ? { float: 'left', overflow: 'hidden' } : { overflow: 'hidden' }
    },
    pos () {
      return {
        transform: `translate(${this.xPos}px,${this.yPos}px)`,
        transition: `all ${this.ease || 'ease-in'} ${this.delay}ms`,
        overflow: 'hidden'
      }
    },
    defaultOption () {
      return {
        step: 1,
        limitMoveNum: 5,
        hoverStop: true,
        direction: 1,
        openTouch: true,
        singleHeight: 0,
        singleWidth: 0,
        waitTime: 1000,
        switchOffset: 30,
        autoPlay: true,
        switchSingleStep: 134,
        switchDelay: 400,
        switchDisabledClass: 'disabled',
        isSingleRemUnit: false
      }
    },
    options () {
      return copyObj({}, this.defaultOption, this.classOption)
    },
    moveSwitch () {
      return this.data.length < this.options.limitMoveNum
    },
    hoverStop () {
      return !this.options.autoPlay || !this.options.hoverStop || this.moveSwitch
    },
    canNotTouch () {
      return !this.options.openTouch || !this.options.autoPlay
    },
    isHorizontal () {
      return this.options.direction > 1 || !this.options.autoPlay
    },
    baseFontSize () {
      return this.options.isSingleRemUnit ? parseInt(window.getComputedStyle(document.documentElement, null).fontSize) : 1
    },
    realSingleStopWidth () {
      return this.options.singleWidth * this.baseFontSize
    },
    realSingleStopHeight () {
      return this.options.singleHeight * this.baseFontSize
    }
  },
  methods: {
    leftSwitchClick () {
      if (!this.leftSwitchState) return
      if (Math.abs(this.xPos) < this.options.switchSingleStep) {
        this.xPos = 0
        return
      }
      this.xPos += this.options.switchSingleStep
    },
    rightSwitchClick () {
      if (!this.rightSwitchState) return
      if ((this.realBoxWidth - this.width + this.xPos) < this.options.switchSingleStep) {
        this.xPos = this.width - this.realBoxWidth
        return
      }
      this.xPos -= this.options.switchSingleStep
    },
    _cancle () {
      cancelAnimationFrame(this.reqFrame || '')
    },
    touchStart (e) {
      if (this.canNotTouch) return
      let timer
      const touch = e.targetTouches[0]
      this.startPos = {
        x: touch.pageX,
        y: touch.pageY
      }
      this.startPosY = this.yPos
      this.startPosX = this.xPos
      if (!!this.options.singleHeight && !!this.options.singleWidth) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          this._cancle()
        }, this.options.waitTime + 20)
      } else {
        this._cancle()
      }
    },
    touchMove (e) {
      if (this.canNotTouch || e.targetTouches.length > 1 || e.scale && e.scale !== 1) return
      const touch = e.targetTouches[0]
      this.endPos = {
        x: touch.pageX - this.startPos.x,
        y: touch.pageY - this.startPos.y
      }
      event.preventDefault();
      const dir = Math.abs(this.endPos.x) < Math.abs(this.endPos.y) ? 1 : 0
      if (dir === 1 && this.options.direction < 2) {
        this.yPos = this.startPosY + this.endPos.y
      } else if (dir === 0 && this.options.direction > 1) {
        this.xPos = this.startPosX + this.endPos.x
      }
    },
    touchEnd () {
      if (this.canNotTouch) return
      let timer
      const direction = this.options.direction
      this.delay = 50
      if (direction === 1) {
        if (this.yPos > 0) this.yPos = 0
      } else if (direction === 0) {
        let h = this.$refs.realBox.offsetHeight / 2 * -1
        if (this.yPos < h) this.yPos = h
      } else if (direction === 2) {
        if (this.xPos > 0) this.xPos = 0
      } else if (direction === 3) {
        let w = this.$refs.slotList.offsetWidth * -1
        if (this.xPos < w) this.xPos = w
      }
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        this.delay = 0
        this._move()
      }, this.delay)
    },
    enter () {
      if (this.hoverStop) return
      this.isHover = true
      if (this.singleWaitTime) clearTimeout(this.singleWaitTime)
      this._cancle()
    },
    leave () {
      if (this.hoverStop) return
      this.isHover = false
      this._move()
    },
    _move () {
      if (this.isHover) return
      this._cancle()
      this.reqFrame = requestAnimationFrame(
        function () {
          if (!this.$refs.realBox) return
          const h = this.$refs.realBox.offsetHeight / 2
          const w = this.$refs.slotList.offsetWidth
          const direction = this.options.direction
          if (direction === 1) {
            if (Math.abs(this.yPos) >= h) {
              this.$emit('ScrollEnd')
              this.yPos = 0
            }
            this.yPos -= this.options.step
          } else if (direction === 0) {
            if (this.yPos >= 0) {
              this.$emit('ScrollEnd')
              this.yPos = h * -1
            }
            this.yPos += this.options.step
          } else if (direction === 2) {
            if (Math.abs(this.xPos) >= w) {
              this.$emit('ScrollEnd')
              this.xPos = 0
            }
            this.xPos -= this.options.step
          } else if (direction === 3) {
            if (this.xPos >= 0) {
              this.$emit('ScrollEnd')
              this.xPos = w * -1
            }
            this.xPos += this.options.step
          }
          if (this.singleWaitTime) clearTimeout(this.singleWaitTime)
          if (this.realSingleStopHeight) {
            if (Math.abs(this.yPos) % this.realSingleStopHeight < 1) {
              this.singleWaitTime = setTimeout(() => {
                this._move()
              }, this.options.waitTime)
            } else {
              this._move()
            }
          } else if (this.realSingleStopWidth) {
            if (Math.abs(this.xPos) % this.realSingleStopWidth < 1) {
              this.singleWaitTime = setTimeout(() => {
                this._move()
              }, this.options.waitTime)
            } else {
              this._move()
            }
          } else {
            this._move()
          }
        }.bind(this)
      )
    },
    _initMove () {
      this.$nextTick(() => {
        this.height = this.$refs.wrap.offsetHeight
        this.width = this.$refs.wrap.offsetWidth
        if (this.isHorizontal) {
          let rate
          if (!this.options.autoPlay) {
            rate = 1
          } else {
            rate = 2
          }
          const w = this.$refs.slotList.offsetWidth * rate
          this.$refs.realBox.style.width = w + 'px'
          this.realBoxWidth = w
        }
        if (!this.options.autoPlay) {
          this.ease = 'linear'
          this.delay = this.options.switchDelay
          return
        }
        this._dataWarm(this.data)
        this.copyHtml = ''
        if (this.moveSwitch) {
          this._cancle()
          this.yPos = this.xPos = 0
        } else {
          let timer
          if (timer) clearTimeout(timer)
          this.copyHtml = this.$refs.slotList.innerHTML
          this._move()
        }
      })
    },
    _dataWarm (data) {
      if (data.length > 100) {
        console.warn(`数据达到了${data.length}条有点多哦~,可能会造成部分老旧浏览器卡顿。`);
      }
    }
  },
  mounted () {
    this._initMove()
  },
  watch: {
    data (newData, oldData) {
      this._dataWarm(newData)
      // 监听data是否有变更
      if (!arrayEqual(newData, oldData)) {
        this._cancle()
        this._initMove()
      }
    }
  },
  beforeDestroy () {
    this._cancle()
  }
}
</script>
